using System;
using HCore.Unity.Core;
using HCore.Unity.Extensions;
using JetBrains.Annotations;
using Session;
using UnityEngine;
using Random = UnityEngine.Random;

namespace Game.Tile
{
    /// <summary>
    ///     这个Mono可能存在大量的对象，不要用 HCore 容器化，会有性能问题。
    /// </summary>
    public class TileVisual : MonoBehaviour
    {
        private SpriteRenderer _TileSpriteRenderer; // Tile的SpriteRenderer
        private Func<Vector2Int, TileVisual> mGetTileVisualFunc; // 根据索引获取 TileVisual
        [CanBeNull] private ISession mSession; // 全局Session
        public TileGroupVisual TileGroupVisual { get; set; } // 当前 Tile 分组
        public Tile Tile { get; set; }

        public SpriteRenderer TileSpriteRenderer
        {
            get
            {
                if (_TileSpriteRenderer == null) _TileSpriteRenderer = GetComponent<SpriteRenderer>();
                return _TileSpriteRenderer;
            }
        }

        /// <summary>
        ///     初始化 TileVisual
        /// </summary>
        /// <param name="getTileVisualFunc">根据 Tile 索引获取 TileVisual</param>
        /// <param name="session">全局Session</param>
        /// <param name="tile">Tile 对象</param>
        /// <param name="originalPos">拼图块的原始坐标（也可以理解为拼图块最终要去到的位置）</param>
        public void Setup(Func<Vector2Int, TileVisual> getTileVisualFunc, ISession session, Tile tile, Vector3 originalPos)
        {
            mGetTileVisualFunc = getTileVisualFunc;
            mSession = session;
            Tile = tile;
            OriginalPos = originalPos;
        }

        #region 固定

        /// <summary>
        ///     拼图块的原始坐标（也可以理解为拼图块最终要去到的位置）
        /// </summary>
        public Vector3 OriginalPos { get; private set; }

        /// <summary>
        ///     已经固定在最终的拼图板 Board 上
        /// </summary>
        private bool isFixed;

        /// <summary>
        ///     最终固定 Tile
        /// </summary>
        public void Fixed()
        {
            if (mSession?.Board == null) return;

            // 设置固定标识
            isFixed = true;
            // 调整到标准位置
            transform.position = OriginalPos;
            // 从受控列表移除
            mSession.Board.TryRemoveControlTile(Tile.Index);
            // 如果有分组，则设置父节点为分组的父节点，同时移除分组
            if (TileGroupVisual != null)
            {
                transform.SetParent(TileGroupVisual.transform.parent);
                TileGroupVisual = null;
            }
        }

        #endregion

        #region 旋转

        private int _RotateIndex;

        /// <summary>
        ///     旋转索引在 0 的时候，才能吸附。而 0 的索引代表已经旋转到正确的角度 0°, -1 代表初始旋转。
        /// </summary>
        private int RotateIndex
        {
            get => _RotateIndex;
            set
            {
                _RotateIndex = _RotateIndex == -1 ? (int)transform.localEulerAngles.z / 90 - 1 : value;
                // 控制不要越界
                if (_RotateIndex < 0) _RotateIndex = 3;
                if (_RotateIndex > 3) _RotateIndex = 0;
                // 正时针旋转（x * -90°）
                transform.localEulerAngles = new Vector3(0, 0, _RotateIndex % 4 * -90);
            }
        }

        /// <summary>
        ///     把 Tile 随机旋转一个角度，一般用在 Tile 初始化后调用一次，当然也可以用在其它需要的地方。
        /// </summary>
        public void RandomRotate()
        {
            _RotateIndex = -1;
            var rotate = Random.Range(20, 340);
            transform.localEulerAngles = new Vector3(0, 0, rotate);
        }

        #endregion

        #region 置顶

        /// <summary>
        ///     排序
        /// </summary>
        private static readonly TileTop _TILE_TOP = new();

        /// <summary>
        ///     Tile 置顶
        /// </summary>
        public void BringToTop()
        {
            _TILE_TOP.BringToTop(TileSpriteRenderer);
        }

        /// <summary>
        ///     清空排序
        /// </summary>
        public static void ClearSorting()
        {
            _TILE_TOP.Clear();
        }

        #endregion

        #region 拖动

        ///// ----------- 
        ///// 拖动分两种，一种是单独拖动一个 Tile ，一种是拖动整个 TileGroup 所以这里分两个逻辑来处理
        ///// 如果存在 TileGroup 则是拖动整个 TileGroup
        ///// ----------- 

        /// <summary>
        ///     当按下鼠标按钮时，此变量将存储鼠标位置和瓷砖位置之间的偏移量。
        /// </summary>
        private Vector3 mOffset = Vector3.zero;

        /// <summary>
        ///     开始拖动的 Tile 坐标
        /// </summary>
        private Vector3 mStartDragPos;

        /// <summary>
        ///     当光标在附加此脚本的GameObject上时，拖动鼠标时，会自动调用此方法。
        ///     在此方法中，我们将在拖动时更新瓷砖的位置。
        /// </summary>
        private void OnMouseDown()
        {
            if (mSession == null) return;
            // 只能在游戏界面
            if (GameManager.GetRouter() != Router.GamePage) return;
            // 准备排序禁止拖动Tile
            if (mSession.IsPreSortingStartedTile) return;
            // 已经固定在最终的拼图板 Board 上
            if (isFixed) return;
            // 鼠标点击到UI上了
            if (GameUtils.IsPointerOverUI()) return;

            // 如果有拼图块组了。 就委托给拼图块组
            if (TileGroupVisual != null)
            {
                TileGroupVisual.DoMouseDown(this);
                return;
            }

            // 这里记录点击瞬间 Tile 所在坐标。方便后面判断是拖动还是点击旋转
            mStartDragPos = transform.position;
            // 保存鼠标位置和瓷砖位置之间的偏移量
            mOffset = transform.position - GameUtils.GetWorldPositionFromScreen();
            // Tile 置顶
            _TILE_TOP.BringToTop(TileSpriteRenderer);
        }

        /// <summary>
        ///     在此方法中，我们将在拖动时更新瓷砖的位置。
        /// </summary>
        private void OnMouseDrag()
        {
            if (mSession == null) return;
            // 只能在游戏界面
            if (GameManager.GetRouter() != Router.GamePage) return;
            // 准备排序禁止拖动Tile
            if (mSession.IsPreSortingStartedTile) return;
            // 已经固定在最终的拼图板 Board 上
            if (isFixed) return;
            // 鼠标点击到UI上了
            if (GameUtils.IsPointerOverUI()) return;

            // 如果有拼图块组了。 就委托给拼图块组
            if (TileGroupVisual != null)
            {
                TileGroupVisual.DoMouseDrag(this);
                return;
            }

            transform.position = GameUtils.GetWorldPositionFromScreen() + mOffset;
        }

        /// <summary>
        ///     如果瓷砖离它足够近，会将瓷砖卡到正确位置。
        /// </summary>
        private void OnMouseUp()
        {
            if (mSession?.Board == null) return;
            // 只能在游戏界面
            if (GameManager.GetRouter() != Router.GamePage) return;
            // 准备排序禁止拖动Tile
            if (mSession.IsPreSortingStartedTile) return;
            // 已经固定在最终的拼图板 Board 上
            if (isFixed) return;
            // 鼠标点击到UI上了
            if (GameUtils.IsPointerOverUI()) return;

            // 如果有拼图块组了。 就委托给拼图块组
            if (TileGroupVisual != null)
            {
                TileGroupVisual.DoMouseUp(this);
                return;
            }

            if (mSession.RotateTile)
            {
                // 这里校验是点击还是拖动。如果是点击，则旋转 Tile 。
                if ((transform.position - mStartDragPos).ToVector2().magnitude < 1)
                {
                    RotateIndex++;
                    return;
                }

                // 如果角度不正确，则跳过
                if (RotateIndex != 0) return;
            }

            ///// 自动吸附到最终拼图板上。
            // 计算当前坐标和实际应该在的坐标间的向量： magnitude - 两点间隔向量
            var originalMagnitude = (OriginalPos - transform.position).ToVector2().magnitude;
            Debug.Log($"TileVisual --- Min Magnitude: {mSession.Board.MinMagnitude}, Original Magnitude: {originalMagnitude}, Current Pos: {transform.position}, Original Pos: {OriginalPos}");
            if (originalMagnitude < mSession.Board.MinMagnitude)
            {
                Debug.Log($"TileVisual({transform.position}) Fixed();");
                Fixed();
            }
            // 未固定，则能放入分组就放入分组。
            ///// 处理 TileGroup，这里又分两种情况，一种是当前 Tile 没有 TileGroup ，则把自己合并到对方的 TileGroup（不存在则创建分组）中。
            ///// 第二种是当前移动的就是 TileGroup 则合并两个 TileGroup。
            //// 这里实现第一种，当前 Tile 没有 TileGroup。  第二种在 TileGroup.DoMouseUp() 里面实现。
            else
            {
                // 找到当前 Tile 四边的锚点。然后以此判定本Tile 是否已经靠近了 另一个 TileGroup（不存在则创建）
                var sides = Utils.GetForeSidesTileControllers(mSession.Board.NumTileX, mSession.Board.NumTileY, Tile.Index);
                foreach (var (dir, index) in sides)
                {
                    // 获取到目标 TileVisual
                    var sideTileVisual = mGetTileVisualFunc(index);
                    // 获取 Tile 之间的补偿值
                    var offset = Utils.GetTileOffset(dir, mSession.Board.TileSize);
                    var sideTilePosition = sideTileVisual.transform.position - offset.ToVector3(0); // 目标点
                    var magnitude = (sideTilePosition - transform.position).ToVector2().magnitude; // 计算当前点和目标点的 magnitude
                    // 小于 MIN_MAGNITUDE 则吸附
                    if (magnitude < mSession.Board.MinMagnitude)
                    {
                        transform.position = sideTilePosition; // 吸附到目标点

                        // 如果匹配上了。则加入 TileGroup（不存在则创建）
                        var targetTileGroup = Utils.GetOrInitTileGroup(sideTileVisual, mGetTileVisualFunc, mSession, transform.parent);
                        // 向 TileGroup 注册 TileController
                        targetTileGroup.Register(this);

                        // 匹配上一个， 就可以直接退出循环。再匹配其它，没有任何意义了。因为已经完成了 Tile 的定位和加入 TileGroup
                        Debug.Log($"TileVisual({transform.position}) Merger;");
                        return;
                    }
                }
            }
        }

        #endregion
    }
}